package com.myk.game.gobangchess.rooms.oldrooms;

import com.myk.game.gobangchess.DataManager;
import com.myk.game.gobangchess.constants.BoardStyleData;
import com.myk.game.gobangchess.constants.WinMode;
import com.myk.game.gobangchess.rooms.absrooms.AbsPersonalRoom;
import com.myk.game.gobangchess.utils.Pair;
import java.util.ArrayList;
import java.util.HashSet;
import java.util.LinkedList;
import java.util.List;
import java.util.Queue;
import java.util.Random;
import java.util.Set;
import java.util.Timer;
import java.util.TimerTask;

public class SoloGravityFourRoom extends AbsPersonalRoom {

    private static final int INIT_NEXT_STONE_ROUND = 3;
    private static final long millisecondForAnimation = 300;
    private static final long millisecondForMove = 300;
    private static final long millisecondForScore = 500;
    private static final int[][] dxdys = new int[][]{{1, 0}, {0, 1}, {1, 1}, {1, -1}};

    private final Queue<Pair<Handler, Long>> handleQue = new LinkedList<>();
    private final Random rand = new Random();
    private final Timer timer = new Timer();
    private int lastCur = 0;
    private int nextStoneRound = INIT_NEXT_STONE_ROUND;
    private final Set<Pair<Integer, Integer>> scorePoints = new HashSet<>();
    private boolean needComputer = false; // 需要电脑落子了吗？
    private Direction curGravity = Direction.DOWN;
    //对弈开始前出现，此时不能落子，点击后按钮消失正式开始对弈

    public SoloGravityFourRoom(String roomName) {
        super(roomName, "引力四子棋合作版本，测试中规则随时会变动。", "合作引力四子棋",
            BoardStyleData.FOUR_GRAVITE_STYLE_6x6, true, false);
    }

    @Override
    public String getRuleText() {
        return "暂未编写。";
    }

    @Override
    public String getCoordMode() {
        return "33332222";
    }

    @Override
    protected void doStart() {
        overFlag = 1;//重置结束状态：对弈进行中未结束
        playingStartTime = System.currentTimeMillis(); //重置对弈开始时间
        DataManager.noticeAllGameStart(player_black, player_white, roomName);
        resetGrids();
        blackScore = 0;
        whiteScore = 0;
        nextStoneRound = INIT_NEXT_STONE_ROUND;
        computerMove();
        handleQue.clear();
        this.curGravity = Direction.UNKNOWN; //初始无引力
        originGrids = BoardStyleData.FOUR_GRAVITE_STYLE_6x6;
        gameTip = "选择重力方向开始游戏";
        noticeBlackMove();
        preHistory(); //准备记录日志
    }

    @Override
    public void resetGrids() {
        super.resetGrids();
    }

    @Override
    public void doRule(int row, int col, int value) {
        if (this.isInGrids(row, col)) {
            // 玩家只能改变引力
            return;
        }
        if (!playerChangeGravity(row, col)) {
            return;
        }

        lastCur = cur;
        needComputer = true;
        nextStoneRound--;
        stepAdd();

        noticeNoMove();

        handleQue.add(new Pair<>(this::updateBoard, millisecondForAnimation));
        handleQue.add(new Pair<>(this::handleCheckStatus, 0L));

        nextHandler();
    }

    private void nextHandler() {
        if (!handleQue.isEmpty()) {
            Pair<Handler, Long> p = handleQue.poll();
            timer.schedule(new TimerTask() {
                @Override
                public void run() {
                    p.first.apply();
                    noticeAllRefreshGameInfo();
                    nextHandler();
                }
            }, p.second);
        }
    }

    private void handleCheckStatus() {
        switch (checkGameStatus()) {
            case 0:
                return;
            case 1:
                handleQue.add(new Pair<>(this::handleCleanScorePoints, millisecondForScore));
                handleQue.add(new Pair<>(this::updateBoard, millisecondForScore));
                handleQue.add(new Pair<>(this::handleCheckStatus, 0L));
                break;
            case 2:
                if (needComputer) {
                    handleQue.add(new Pair<>(this::handleComputerMove, millisecondForMove));
                } else {
                    if (lastCur == 1) {
                        noticeWhiteMove();
                    } else {
                        noticeBlackMove();
                    }
                }
                break;
        }
    }

    private void handleCleanScorePoints() {
        for (Pair<Integer, Integer> pair : scorePoints) {
            curGrids[pair.first][pair.second] = 0;
            for (int[] dxdy : dxdys) {
                {
                    int x = pair.first + dxdy[0];
                    int y = pair.second + dxdy[1];
                    if (curGrids[x][y] == 7) {
                        curGrids[x][y] = 0;
                    }
                }
                {
                    int x = pair.first - dxdy[0];
                    int y = pair.second - dxdy[1];
                    if (curGrids[x][y] == 7) {
                        curGrids[x][y] = 0;
                    }
                }
            }
        }
        scorePoints.clear();
    }

    private void handleComputerMove() {
        computerMove();

        handleQue.add(new Pair<>(this::updateBoard, millisecondForAnimation));
        handleQue.add(new Pair<>(this::handleCheckStatus, 0L));
    }

    private void computerMove() {

        List<Integer> indexList = new ArrayList<>();

        for (int x = gridRowMin(); x <= gridRowMax(); x++) {
            for (int y = gridColMin(); y <= gridColMax(); y++) {
                if (curGrids[x][y] == 0) {
                    indexList.add(getIndex(x, y));
                }
            }
        }
        if (nextStoneRound == 0 && !indexList.isEmpty()) {
            nextStoneRound = rand.nextInt(2) + 1;
            int randomI = rand.nextInt(indexList.size());
            int randomIndex = indexList.remove(randomI);
            int[] pos = getPositionByIndex(randomIndex);
            curGrids[pos[0]][pos[1]] = 7;
        }
        boolean valueChange = rand.nextBoolean();
        for (int value = 1; value < 3; value++) {
            if (indexList.isEmpty()) {
                continue;
            }
            int randomI = rand.nextInt(indexList.size());
            int randomIndex = indexList.remove(randomI);
            int[] pos = getPositionByIndex(randomIndex);
            if (valueChange) {
                curGrids[pos[0]][pos[1]] = 3 - value;
            } else {
                curGrids[pos[0]][pos[1]] = value;
            }
        }

        needComputer = false;
        gameTip = String.format("距离障碍物出现还有%s回合", nextStoneRound);
    }

    /**
     * @return 是否成功
     */
    private boolean playerChangeGravity(int row, int col) {
        if ((row < gridRowMin() || row > gridRowMax()) && (col < gridRowMin() || col > gridRowMax())) {
            // 忽略四个角落
            return false;
        }

        Direction selectDirection;
        if (row < gridRowMin()) {
            selectDirection = Direction.UP;
        } else if (row > gridRowMax()) {
            selectDirection = Direction.DOWN;
        } else if (col < gridRowMin()) {
            selectDirection = Direction.LEFT;
        } else if (col > gridRowMax()) {
            selectDirection = Direction.RIGHT;
        } else {
            // 应该是不会到这儿的
            //System.out.println("判断方向出错啦！");
            return false;
        }

        // 并没有改变引力
        if (this.curGravity == selectDirection) {
            return false;
        }

        signList.clear();

        // 修改引力
        this.updateGravity(selectDirection);
        return true;
    }

    // 棋盘row能达到的小边界
    protected int gridRowMin() {
        return 2;
    }

    // 棋盘row能达到的大边界
    protected int gridRowMax() {
        return curGrids.length - 3;
    }

    // 棋盘col能达到的小边界
    protected int gridColMin() {
        return 2;
    }

    // 棋盘col能达到的大边界
    protected int gridColMax() {
        return curGrids[0].length - 3;
    }

    // 判断位置在棋盘上
    private boolean isInGrids(int row, int col) {
        return row >= gridRowMin() && row <= gridRowMax() && col >= gridColMin() && col <= gridColMax();
    }

    // 交换棋盘上两个棋子的位置
    private void swapInGrids(int r1, int c1, int r2, int c2) {
        int tmp = curGrids[r1][c1];
        curGrids[r1][c1] = curGrids[r2][c2];
        curGrids[r2][c2] = tmp;
    }

    // 根据 this.curGravity 更新棋盘
    private void updateBoard() {
        switch (this.curGravity) {
            case DOWN:
                for (int col = gridColMin(); col <= gridColMax(); col++) {
                    int p1 = gridRowMax();
                    int p2 = p1;
                    while (p2 >= gridRowMin()) {
                        if (curGrids[p2][col] != 0) {
                            this.swapInGrids(p1, col, p2, col);
                            p1--;
                        }
                        p2--;
                    }
                }
                break;
            case UP:
                for (int col = gridColMin(); col <= gridColMax(); col++) {
                    int p1 = gridRowMin();
                    int p2 = p1;
                    while (p2 <= gridRowMax()) {
                        if (curGrids[p2][col] != 0) {
                            this.swapInGrids(p1, col, p2, col);
                            p1++;
                        }
                        p2++;
                    }
                }
                break;
            case LEFT:
                for (int row = gridRowMin(); row <= gridRowMax(); row++) {
                    int p1 = gridColMin();
                    int p2 = p1;
                    while (p2 <= gridColMax()) {
                        if (curGrids[row][p2] != 0) {
                            this.swapInGrids(row, p1, row, p2);
                            p1++;
                        }
                        p2++;
                    }
                }
                break;
            case RIGHT:
                for (int row = gridRowMin(); row <= gridRowMax(); row++) {
                    int p1 = gridColMax();
                    int p2 = p1;
                    while (p2 >= gridColMin()) {
                        if (curGrids[row][p2] != 0) {
                            this.swapInGrids(row, p1, row, p2);
                            p1--;
                        }
                        p2--;
                    }
                }
                break;
            default:
                // 应该是不会到这儿的
                //System.out.println("更新棋盘时方向出错啦！");
        }
    }

    private void updateGravity(Direction d) {
        Set<Integer> StopSet = new HashSet<>();
        for (int i = 0; i < curGrids.length; i++) {
            curGrids[i][0] = 0;
            curGrids[i][curGrids[0].length - 1] = 0;
            StopSet.add(getIndex(i, 0));
            StopSet.add(getIndex(i, curGrids[0].length - 1));
        }
        for (int i = 0; i < curGrids[0].length; i++) {
            curGrids[0][i] = 0;
            curGrids[curGrids.length - 1][i] = 0;
            StopSet.add(getIndex(0, i));
            StopSet.add(getIndex(curGrids[0].length - 1, i));
        }
        Set<Integer> PutChessSet = new HashSet<>();
        switch (d) {
            case DOWN:
                for (int i = 0; i < curGrids[0].length; i++) {
                    curGrids[curGrids.length - 1][i] = 9;
                    PutChessSet.add(getIndex(curGrids.length - 1, i));
                    StopSet.remove(getIndex(curGrids.length - 1, i));
                }
                break;
            case UP:
                for (int i = 0; i < curGrids[0].length; i++) {
                    curGrids[0][i] = 9;
                    PutChessSet.add(getIndex(0, i));
                    StopSet.remove(getIndex(0, i));
                }
                break;
            case LEFT:
                for (int i = 0; i < curGrids.length; i++) {
                    curGrids[i][0] = 9;
                    PutChessSet.add(getIndex(i, 0));
                    StopSet.remove(getIndex(i, 0));
                }
                break;
            case RIGHT:
                for (int i = 0; i < curGrids.length; i++) {
                    curGrids[i][curGrids[0].length - 1] = 9;
                    PutChessSet.add(getIndex(i, curGrids[0].length - 1));
                    StopSet.remove(getIndex(i, curGrids[0].length - 1));
                }
                break;
            default:
                // 应该是不会到这儿的
                //System.out.println("更新引力方向时出错啦！");
                return;
        }
        this.curGravity = d;
    }

    protected Set<Pair<Integer, Integer>> tryGetScore(int x, int y, int dx, int dy, int value) {
        Set<Pair<Integer, Integer>> result = new HashSet<>();
        while (isInGrids(x, y)) {
            if (curGrids[x][y] != value) {
                return result;
            }
            result.add(new Pair<>(x, y));
            x += dx;
            y += dy;
        }
        return result;
    }

    /**
     * @return 0 游戏结束 1 有得分 2 无得分
     */
    public int checkGameStatus() {
        boolean gridFull = true;
        scorePoints.clear();
        for (int x = gridRowMin(); x <= gridRowMax(); x++) {
            for (int y = gridColMin(); y <= gridColMax(); y++) {
                if (curGrids[x][y] == 0) {
                    gridFull = false;
                }
                final int value = curGrids[x][y];
                if (value != 1 && value != 2) {
                    continue;
                }
                for (int[] dxdy : dxdys) {
                    int dx = dxdy[0];
                    int dy = dxdy[1];
                    Set<Pair<Integer, Integer>> scoreLine = new HashSet<>();
                    scoreLine.addAll(tryGetScore(x, y, dx, dy, value));
                    scoreLine.addAll(tryGetScore(x, y, -dx, -dy, value));
                    if (scoreLine.size() == 4) {
                        boolean newScore = scoreLine.stream().anyMatch(p -> !scorePoints.contains(p));
                        scorePoints.addAll(scoreLine);
                        if (newScore) {
                            if (lastCur == 1) {
                                blackScore++;
                            } else if (lastCur == 2) {
                                whiteScore++;
                            }
                        }
                    }
                }
            }
        }
        if (!scorePoints.isEmpty()) {
            return 1;
        }
        if (gridFull) {
            if (player_black == player_white) {
                doOver(WinMode.BOTH_DRAW, "单人游戏结束，得分：" + (blackScore + whiteScore));
            } else if (blackScore > whiteScore) {
                doOver(WinMode.BLACK_WIN, String.format("双人合作游戏结束，黑方（%s）主力得分", player_black.getNickNameOrTemp()));
            } else if (whiteScore > blackScore) {
                doOver(WinMode.WHITE_WIN, String.format("双人合作游戏结束，白方（%s）主力得分", player_white.getNickNameOrTemp()));
            } else {
                doOver(WinMode.BOTH_DRAW, "双人合作游戏结束，双方得分一样高");
            }
            return 0;
        }
        return 2;
    }

    enum Direction {
        UNKNOWN, UP, DOWN, LEFT, RIGHT
    }

    @FunctionalInterface
    private interface Handler {

        void apply();
    }

}
